package service

import (
	"context"
	"errors"
	"pay_service/biz/model"
	"pay_service/biz/model/orm/paystatus"
	"pay_service/biz/model/orm_gen"
	"pay_service/biz/model/query"
	"pay_service/biz/pack"
	"pay_service/internal/incid"
	"strconv"
	"time"

	mw "pay_service/biz/middleware"

	jwtModel "pay_service/biz/model/jwt"

	"github.com/cloudwego/hertz/pkg/app"
)

type PayService struct {
	ctx context.Context
	c   *app.RequestContext
}

func NewPayService(ctx context.Context, c *app.RequestContext) *PayService {
	return &PayService{ctx, c}
}

func (p *PayService) ApplyPayOrder(req *model.ApplyPayOrderRequest) (string, error) {
	payOrder, err := p.checkIdempotent(req)
	if err != nil {
		return "", err
	}
	return strconv.FormatInt(payOrder.Id, 10), nil
}

// @Transactional
func (p *PayService) TryPayOrderByBalance(req *model.TryPayOrderByBalanceRequest) error {
	return query.Q.Transaction(func(tx *query.Query) error {
		dPayOrder := query.PayOrder
		id := req.Id
		po, err := dPayOrder.Select(dPayOrder.Status).Where(dPayOrder.BizOrderNo.Eq(id)).First()
		if err != nil {
			return errors.New("[pay]: can not find the payOrder")
		}
		if po.Status != paystatus.WAIT_BUYER_PAY {
			return errors.New("[pay]: current trade is paid or closed")
		}
		// TODO: user service to deductMoney
		_, err = dPayOrder.
			Where(
				dPayOrder.ID.Eq(id),
				dPayOrder.Status.In(paystatus.NOT_COMMIT, paystatus.WAIT_BUYER_PAY),
			).
			UpdateSimple(
				dPayOrder.Status.Value(paystatus.TRADE_SUCCESS),
				dPayOrder.PaySuccessTime.Value(time.Now()),
			)
		if err != nil {
			return errors.New("[pay]: the trade is closed or success")
		}
		// TODO: order service to get order model
		return nil
	})
}

func (p *PayService) AllPayOrders() ([]*model.PayOrderDTO, error) {
	dPayOrder := query.PayOrder
	result, err := dPayOrder.Find()
	if err != nil {
		return nil, err
	}
	return pack.PayOrders(result), nil
}

func (p *PayService) PayOrderByBizId(bizno int64) (*model.PayOrderDTO, error) {
	dPayOrder := query.PayOrder
	result, err := dPayOrder.Where(dPayOrder.BizOrderNo.Eq(bizno)).First()
	if err != nil {
		return nil, err
	}
	return pack.PayOrder(result), nil
}

// Return the uesr_id from the context
func (p *PayService) getUserId() int64 {
	user := p.c.Value(mw.JWTIdentityKey).(*jwtModel.JWTUser)
	return user.UserID
}

// Return a default PayOrder
func (p *PayService) buildPayOrder(dto *model.ApplyPayOrderRequest) *orm_gen.PayOrder {
	res := &orm_gen.PayOrder{
		BizOrderNo:     dto.BizOrderNo,
		PayChannelCode: dto.PayChannelCode,
		Amount:         dto.Amount,
		PayType:        dto.PayType,
	}
	res.Status = paystatus.NOT_COMMIT
	res.PayOverTime = time.Now()
	res.BizUserID = p.getUserId()
	return res
}

// Return the PayOrderDTO bind to bizOrderNo when the payOrder was not finished
func (p *PayService) checkIdempotent(req *model.ApplyPayOrderRequest) (*model.PayOrderDTO, error) {
	no := req.GetBizOrderNo()
	dPayOrder := query.PayOrder
	oldOrder, err := dPayOrder.Select(dPayOrder.BizOrderNo).Where(dPayOrder.BizOrderNo.Eq(no)).First()
	if err != nil {
		payOrder := p.buildPayOrder(req)
		payOrder.PayOrderNo = incid.GetId()
		dPayOrder.Save(payOrder)
		return pack.PayOrder(payOrder), nil
	}
	if oldOrder.Status == paystatus.TRADE_SUCCESS {
		return nil, errors.New("[pay]: the trade is success, should not be repeated")
	}
	if oldOrder.Status == paystatus.TRADE_CLOSED {
		return nil, errors.New("[pay]: the trade is closed")
	}
	if oldOrder.PayChannelCode != req.PayChannelCode {
		payOrder := p.buildPayOrder(req)
		payOrder.ID = oldOrder.ID
		payOrder.PayChannelCode = ""
		_, err := dPayOrder.Where(dPayOrder.ID.Eq(payOrder.ID)).Updates(payOrder)
		if err != nil {
			return nil, err
		}
		payOrder.PayOrderNo = oldOrder.PayOrderNo
		return pack.PayOrder(payOrder), nil
	}
	return pack.PayOrder(oldOrder), nil
}
